#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <fcntl.h>
#include <math.h>
#include <errno.h>
#include <assert.h>
#include <malloc.h>
#include <fstream>

#include <unistd.h>

#include <linux/videodev2.h>

#define BUFFER_COUNT 4
#define CLEAR(x) memset(&(x), 0, sizeof(x))
using namespace std;
FILE *file_fd;
const char *file_name[] = {"1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg", "6.jpg", "7.jpg", "8.jpg", "9.jpg", "10.jpg"};
int file_index = 0;

typedef struct VideoBuffer
{
    void *start;
    size_t length;
} VideoBuffer;

typedef __u64 v4l2_std_id;

static void errno_exit(const char *s)
{
    fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno));
    exit(EXIT_FAILURE);
}

int main(int argc, char *argv[])
{

    int cameraFd, ret, numBufs;

    cameraFd = open("/dev/video0", O_RDWR, 0); //设备节点由具体设备改变/dev/xxxxx 非阻塞方式打开
    printf("The cameraFd is %d \n", cameraFd);

    struct v4l2_capability cap;
    ret = ioctl(cameraFd, VIDIOC_QUERYCAP, &cap); //VIDIOC_QUERYCAP：查询驱动功能
    if (ret < 0)
    {
        printf("VIDIOC_QUERYCAP failed (%d)\n", ret);
        return ret;
    }

    struct v4l2_format fmt;
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = 640;  //640
    fmt.fmt.pix.height = 480; //480
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    fmt.fmt.pix.field = V4L2_FIELD_ANY;
    ret = ioctl(cameraFd, VIDIOC_S_FMT, &fmt);
    if (ret < 0) //VIDIOC_S_FMT：设置当前驱动的频捕获格式
    {
        printf("VIDIOC_S_FMT failed (%d)\n", ret);
        return ret;
    }

    struct v4l2_requestbuffers reqbuf;
    reqbuf.count = BUFFER_COUNT;
    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuf.memory = V4L2_MEMORY_MMAP;
    printf("The requestbuffers size is %d \n", sizeof(reqbuf));

    if (ioctl(cameraFd, VIDIOC_REQBUFS, &reqbuf) == -1)
    {
        printf("Rquestbuffers fail..\n");
        return ret;
    }

    VideoBuffer *buffers = (VideoBuffer *)calloc(reqbuf.count, sizeof(*buffers)); //在内存中分配4个Buffer
    struct v4l2_buffer buf;
    printf("\n");
    printf("The buffers addres = 0x%x\n", (unsigned int *)&buf);
    printf("The buf.length    = %d \n", buf.length);
    printf("\n");
    for (numBufs = 0; numBufs < reqbuf.count; numBufs++)
    {
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = numBufs; //索引分配的buffers个数在以后的运算中有用
        // 读取缓存
        ret = ioctl(cameraFd, VIDIOC_QUERYBUF, &buf);
        if (ret < 0)
        {
            printf("VIDIOC_REQBUFS fail ..\n");
            return ret;
        }

        buffers[numBufs].start = (char *)mmap(0, buf.length, PROT_READ | PROT_WRITE,
                                              MAP_SHARED, cameraFd, buf.m.offset);

        printf("The buf.length = %d \n", buf.length);

        if (buffers[numBufs].start == MAP_FAILED)
        {
            return -1;
        }

        // 放入缓存队列
        ret = ioctl(cameraFd, VIDIOC_QBUF, &buf);
        if (ret < 0)
        {
            printf("VIDIOC_QBUF failed (%d)\n", ret);
            return ret;
        }
        printf("buffers[%d].start = 0x%x, length=%d\n", numBufs, (unsigned int *)buffers[numBufs].start, sizeof(buffers[numBufs]));
        printf("The buf.index = %d \n", buf.index);
    }

    //获取数据到文件
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(cameraFd, VIDIOC_STREAMON, &type);
    if (ret < 0)
    {
        printf("VIDIOC_STREAMON failed (%d)\n", ret);
        return 0;
    }

    int num = 0;
    char pic_buf[100 * 1024] = {'0'};
    fstream fs("myyuv.yuv",ios::out|ios::binary);
    while (1)
    {
        if (num > 5000) 
        {
            break;
        }
        int r, read_ret;
        
        // CLEAR (buf);
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        do
            r = ioctl(cameraFd, VIDIOC_DQBUF, &buf);
        while (-1 == r && EINTR == errno);
        if ((ret == ioctl(cameraFd, VIDIOC_DQBUF, &buf)) < 0)
        {
            switch (errno)
            {
            case EAGAIN:
                return 0;
            case EIO:

            default:
                printf("VIDIOC_DQBUF failed (%d)\n", ret);
            }
        }
        assert(buf.index < numBufs);
        if (file_index == 10)
        {
            file_index = 0;
            printf("you got 10 pic file_index = %d \n", file_index);
        }
        
        // FILE *fp = fopen(file_name[file_index++], "wb");
        // fputc('#', stdout);
        // if (buf.length > 0)
        // {
        //     fputc('#', stdout);
        //     fwrite(buffers[buf.index].start, 1, buf.length / 6, fp);
        // }
        // fflush(stdout);
        // fclose(fp);
        fs.write((const char*)buffers[buf.index].start, buf.length);
        ret = ioctl(cameraFd, VIDIOC_QBUF, &buf);
        if (ret < 0)
        {
            printf("VIDIOC_QBUF failed (%d)\n", ret);
            return ret;
        }
        num ++;
    }
    // 释放内存 清空buffer区
    for (numBufs = 0; numBufs < reqbuf.count; numBufs++)
    {
        munmap(buffers[numBufs].start, buffers[numBufs].length);
    }
    fs.close();
    close(cameraFd);
    printf("The camera is close \n");
    return 0;
}